#define _GNU_SOURCE
//#define __COMPILE_DEBUG__

#include <sys/types.h>
#include "zepto.h"
#include "gc.h"
#include <stdlib.h>
#include <search.h>
#include <string.h>
#include <errno.h>

#define RECORDS(x)                              \
  ((x)->data.env.records)

#define SUPER(x)                                \
  ((x)->data.env.super)

typedef struct __env_node {
  unsigned long key;
  struct zepto *value;
} env_node_t;

struct env toplevel_env = {
  .records = NULL,
};

static int node_compare(const void *a, const void *b)
{
  return ((env_node_t *) a)->key - ((env_node_t *) b)->key;
}

static void *__lookup(struct env *env, unsigned long key)
{
  env_node_t **tree_res;
  env_node_t tree_node;
  void *ret = NULL;
  
  tree_node.key = key;
  tree_res = (env_node_t **)tfind(&tree_node, &env->records, node_compare);
  ret = tree_res ? ((*tree_res)->value) : NULL;

  return ret;
}

static int __set(struct env *env, unsigned long key, void *value)
{
  env_node_t *tree_new;
  env_node_t **tree_res;
  int ret = 0;

  DEBUG("Tree update");
  tree_new = zepto_alloc_general(sizeof(*tree_new));
  tree_new->key = key;
  tree_res = (env_node_t **)tfind(tree_new, &env->records, node_compare);
  if(!tree_res) {
    DEBUG("Not found in tree");
    ret = -1;
    goto out;
  }
  
  (*tree_res)->value = value;
  DEBUG("Updated.");

  out:
  return ret;
}

static int __define(struct env *env, unsigned long key, void *value)
{
  env_node_t *tree_new;
  env_node_t **tree_res;
  int ret = 0;

  DEBUG("Tree insert");
  tree_new = zepto_alloc_general(sizeof(*tree_new));
  tree_new->key = key;
  
  WITH_GC(tree_res = (env_node_t **)tsearch(
            tree_new, &env->records, node_compare),
          tree_res != NULL, "inserting into tree");
  
  if(*tree_res != tree_new) {
    // already inserted
    DEBUG("Already inserted");
    (*tree_res)->value = value;
    free(tree_new);
  } else {
    DEBUG("Not already inserted");
    tree_new->value = value;
  }

  return ret;
}

struct env *env_create(struct env *super)
{
  struct env *env;
  
  env = zepto_alloc_env();
  env->super = super;
  env->records = NULL;
  
  return env;
}

struct zepto *env_lookup(struct env *env, struct zepto *key)
{
  struct zepto *ret;

  while(env) {
    DEBUG("Env %p key %s super env %p", env, key->data.symbol, env->super);
    ret = __lookup(env, (unsigned long)key);
    DEBUG("returned %p", ret);
    if(ret) {
      break;
    }
    env = env->super;
  }

  return ret;
}

int env_define(struct env *env, struct zepto *key, struct zepto *value)
{

  DEBUG("Defining %s", key->data.symbol);
  return __define(env, (unsigned long)key, (void *)value);
}

int env_set(struct env *env, struct zepto *key, struct zepto *value)
{
  int ret = -1;
  
  while(env) {
    DEBUG("Env %p key %s env super %p", env, key->data.symbol, env->super);
    ret = __set(env, (unsigned long)key, (void *)value);
    DEBUG("returned %d", ret);
    if(ret == 0) break;
    env = env->super;
  }

  if(ret != 0) {
    ERROR("Inexistent variable.\n");
  }

  return 0;
}

int env_init_toplevel(size_t size)
{
  return 0;
}

static void free_node(void *node)
{
  free((void *)((env_node_t *)node)->key);
  free(node);
}

void env_destroy(struct env *env)
{
  tdestroy(env->records, free_node);
  free(env);
}

